<template>
  <div>
    <el-dialog v-dialogDrag top="5vh" custom-class="rtc-private-video-dialog" :title="title" :width="width"
      :visible.sync="showRoom" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="onQuit">
      <div class="rtc-private-video">
        <div v-show="isVideo" class="rtc-video-box">
          <div class="rtc-video-friend" v-loading="!isChating" element-loading-text="等待对方接听..."
            element-loading-background="rgba(0, 0, 0, 0.1)">
            <head-image class="friend-head-image" :id="friend.id" :size="80" :name="friend.nickName"
              :url="friend.headImage" :isShowUserInfo="false" radius="0">
            </head-image>
            <video ref="remoteVideo" autoplay=""></video>
          </div>
          <div class="rtc-video-mine">
            <video ref="localVideo" autoplay=""></video>
          </div>
        </div>
        <div v-show="!isVideo" class="rtc-voice-box" v-loading="!isChating" element-loading-text="等待对方接听..."
          element-loading-background="rgba(0, 0, 0, 0.1)">
          <head-image class="friend-head-image" :id="friend.id" :size="200" :name="friend.nickName"
            :url="friend.headImage" :isShowUserInfo="false">
            <div class="rtc-voice-name">{{ friend.nickName }}</div>
          </head-image>
        </div>
        <div class="rtc-control-bar">
          <div title="取消" class="icon iconfont icon-phone-reject reject" style="color: red;" @click="onQuit()"></div>
        </div>
      </div>
    </el-dialog>
    <rtc-private-acceptor v-if="!isHost && isWaiting" ref="acceptor" :friend="friend" :mode="mode" @accept="onAccept"
      @reject="onReject"></rtc-private-acceptor>
  </div>
</template>

<script>
import HeadImage from '../common/HeadImage.vue';
import RtcPrivateAcceptor from './RtcPrivateAcceptor.vue';
import ImWebRtc from '@/api/webrtc';
import ImCamera from '@/api/camera';
import RtcPrivateApi from '@/api/rtcPrivateApi'

export default {
  name: 'rtcPrivateVideo',
  components: {
    HeadImage,
    RtcPrivateAcceptor
  },
  data() {
    return {
      camera: new ImCamera(), // 摄像头和麦克风
      webrtc: new ImWebRtc(), // webrtc相关
      API: new RtcPrivateApi(), // API
      audio: new Audio(), // 呼叫音频
      showRoom: false,
      friend: {},
      isHost: false, // 是否发起人
      state: "CLOSE", // CLOSE:关闭  WAITING:等待呼叫或接听 CHATING:聊天中  ERROR:出现异常
      mode: 'video', // 模式 video:视频聊 voice:语音聊天
      localStream: null, // 本地视频流
      remoteStream: null, // 对方视频流
      videoTime: 0,
      videoTimer: null,
      heartbeatTimer: null,
      candidates: [],
    }
  },
  methods: {
    open(rtcInfo) {
      this.showRoom = true;
      this.mode = rtcInfo.mode;
      this.isHost = rtcInfo.isHost;
      this.friend = rtcInfo.friend;
      if (this.isHost) {
        this.onCall();
      }
    },
    initAudio() {
      let url = require(`@/assets/audio/call.wav`);
      this.audio.src = url;
      this.audio.loop = true;
    },
    initRtc() {
      this.webrtc.init(this.configuration)
      this.webrtc.setupPeerConnection((stream) => {
        this.$refs.remoteVideo.srcObject = stream;
        this.remoteStream = stream;
      })
      // 监听候选信息
      this.webrtc.onIcecandidate((candidate) => {
        if (this.state == "CHATING") {
          // 连接已就绪,直接发送
          this.API.sendCandidate(this.friend.id, candidate);
        } else {
          // 连接未就绪,缓存起来，连接后再发送
          this.candidates.push(candidate)
        }
      })
      // 监听连接成功状态
      this.webrtc.onStateChange((state) => {
        if (state == "connected") {
          console.log("webrtc连接成功")
        } else if (state == "disconnected") {
          console.log("webrtc连接断开")
        }
      })
    },
    onCall() {
      if (!this.checkDevEnable()) {
        this.close();
      }
      // 初始化webrtc
      this.initRtc();
      // 启动心跳
      this.startHeartBeat();
      // 打开摄像头
      this.openStream().then(() => {
        this.webrtc.setStream(this.localStream);
        this.webrtc.createOffer().then((offer) => {
          // 发起呼叫
          this.API.call(this.friend.id, this.mode, offer).then(() => {
            // 直接进入聊天状态
            this.state = "WAITING";
            // 播放呼叫铃声
            this.audio.play();
          }).catch(() => {
            this.close();
          })
        })
      }).catch(() => {
        // 呼叫方必须能打开摄像头，否则无法正常建立连接
        this.close();
      })
    },
    onAccept() {
      if (!this.checkDevEnable()) {
        this.API.failed(this.friend.id, "对方设备不支持通话")
        this.close();
        return;
      }
      // 进入房间
      this.showRoom = true;
      this.state = "CHATING";
      // 停止呼叫铃声
      this.audio.pause();
      // 初始化webrtc
      this.initRtc();
      // 打开摄像头
      this.openStream().finally(() => {
        this.webrtc.setStream(this.localStream);
        this.webrtc.createAnswer(this.offer).then((answer) => {
          this.API.accept(this.friend.id, answer);
          // 记录时长
          this.startChatTime();
          // 清理定时器
          this.waitTimer && clearTimeout(this.waitTimer);
        })
      })
    },
    onReject() {
      console.log("onReject")
      // 退出通话
      this.API.reject(this.friend.id);
      // 退出
      this.close();
    },
    onHandup() {
      this.API.handup(this.friend.id)
      this.$message.success("您已挂断,通话结束")
      this.close();
    },
    onCancel() {
      this.API.cancel(this.friend.id)
      this.$message.success("已取消呼叫,通话结束")
      this.close();
    },
    onRTCMessage(msg) {
      // 除了发起通话，如果在关闭状态就无需处理
      if (msg.type != this.$enums.MESSAGE_TYPE.RTC_CALL_VOICE &&
        msg.type != this.$enums.MESSAGE_TYPE.RTC_CALL_VIDEO &&
        this.isClose) {
        return;
      }
      // RTC信令处理
      switch (msg.type) {
        case this.$enums.MESSAGE_TYPE.RTC_CALL_VOICE:
          this.onRTCCall(msg, 'voice')
          break;
        case this.$enums.MESSAGE_TYPE.RTC_CALL_VIDEO:
          this.onRTCCall(msg, 'video')
          break;
        case this.$enums.MESSAGE_TYPE.RTC_ACCEPT:
          this.onRTCAccept(msg)
          break;
        case this.$enums.MESSAGE_TYPE.RTC_REJECT:
          this.onRTCReject(msg)
          break;
        case this.$enums.MESSAGE_TYPE.RTC_CANCEL:
          this.onRTCCancel(msg)
          break;
        case this.$enums.MESSAGE_TYPE.RTC_FAILED:
          this.onRTCFailed(msg)
          break;
        case this.$enums.MESSAGE_TYPE.RTC_HANDUP:
          this.onRTCHandup(msg)
          break;
        case this.$enums.MESSAGE_TYPE.RTC_CANDIDATE:
          this.onRTCCandidate(msg)
          break;
      }
    },
    onRTCCall(msg, mode) {
      this.offer = JSON.parse(msg.content);
      this.isHost = false;
      this.mode = mode;
      this.$http({
        url: `/friend/find/${msg.sendId}`,
        method: 'get'
      }).then((friend) => {
        this.friend = friend;
        this.state = "WAITING";
        this.audio.play();
        this.startHeartBeat();
        // 30s未接听自动挂掉
        this.waitTimer = setTimeout(() => {
          this.API.failed(this.friend.id, "对方无应答");
          this.$message.error("您未接听");
          this.close();
        }, 30000)
      })
    },
    onRTCAccept(msg) {
      if (msg.selfSend) {
        // 在其他设备接听
        this.$message.success("已在其他设备接听");
        this.close();
      } else {
        // 对方接受了的通话
        let offer = JSON.parse(msg.content);
        this.webrtc.setRemoteDescription(offer);
        // 状态为聊天中
        this.state = 'CHATING'
        // 停止播放语音
        this.audio.pause();
        // 发送candidate
        this.candidates.forEach((candidate) => {
          this.API.sendCandidate(this.friend.id, candidate);
        })
        // 开始计时
        this.startChatTime()
      }
    },
    onRTCReject(msg) {
      if (msg.selfSend) {
        this.$message.success("已在其他设备拒绝");
        this.close();
      } else {
        this.$message.error("对方拒绝了您的通话请求");
        this.close();
      }
    },
    onRTCFailed(msg) {
      // 呼叫失败
      this.$message.error(msg.content)
      this.close();
    },
    onRTCCancel() {
      // 对方取消通话
      this.$message.success("对方取消了呼叫");
      this.close();
    },
    onRTCHandup() {
      // 对方挂断
      this.$message.success("对方已挂断");
      this.close();
    },
    onRTCCandidate(msg) {
      let candidate = JSON.parse(msg.content);
      this.webrtc.addIceCandidate(candidate);
    },

    openStream() {
      return new Promise((resolve, reject) => {
        if (this.isVideo) {
          // 打开摄像头+麦克风
          this.camera.openVideo().then((stream) => {
            this.localStream = stream;
            this.$nextTick(() => {
              this.$refs.localVideo.srcObject = stream;
              this.$refs.localVideo.muted = true;
            })
            resolve(stream);
          }).catch((e) => {
            this.$message.error("打开摄像头失败")
            console.log("本摄像头打开失败:" + e.message)
            reject(e);
          })
        } else {
          // 打开麦克风
          this.camera.openAudio().then((stream) => {
            this.localStream = stream;
            this.$refs.localVideo.srcObject = stream;
            resolve(stream);
          }).catch((e) => {
            this.$message.error("打开麦克风失败")
            console.log("打开麦克风失败:" + e.message)
            reject(e);
          })
        }
      })
    },
    startChatTime() {
      this.videoTime = 0;
      this.videoTimer && clearInterval(this.videoTimer);
      this.videoTimer = setInterval(() => {
        this.videoTime++;
      }, 1000)
    },
    checkDevEnable() {
      // 检测摄像头
      if (!this.camera.isEnable()) {
        this.message.error("访问摄像头失败");
        return false;
      }
      // 检测webrtc
      if (!this.webrtc.isEnable()) {
        this.message.error("初始化RTC失败，原因可能是: 1.服务器缺少ssl证书 2.您的设备不支持WebRTC");
        return false;
      }
      return true;
    },
    startHeartBeat() {
      // 每15s推送一次心跳
      this.heartbeatTimer && clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = setInterval(() => {
        this.API.heartbeat(this.friend.id);
      }, 15000)
    },
    close() {
      this.showRoom = false;
      this.camera.close();
      this.webrtc.close();
      this.audio.pause();
      this.videoTime = 0;
      this.videoTimer && clearInterval(this.videoTimer);
      this.heartbeatTimer && clearInterval(this.heartbeatTimer);
      this.waitTimer && clearTimeout(this.waitTimer);
      this.videoTimer = null;
      this.heartbeatTimer = null;
      this.waitTimer = null;
      this.state = 'CLOSE';
      this.candidates = [];
    },
    onQuit() {
      if (this.isChating) {
        this.onHandup()
      } else if (this.isWaiting) {
        this.onCancel();
      } else {
        this.close();
      }
    }
  },
  computed: {
    width() {
      return this.isVideo ? '960px' : '360px'
    },
    title() {
      let strTitle = `${this.modeText}通话-${this.friend.nickName}`;
      if (this.isChating) {
        strTitle += `(${this.currentTime})`;
      } else if (this.isWaiting) {
        strTitle += `(呼叫中)`;
      }
      return strTitle;
    },
    currentTime() {
      let min = Math.floor(this.videoTime / 60);
      let sec = this.videoTime % 60;
      let strTime = min < 10 ? "0" : "";
      strTime += min;
      strTime += ":"
      strTime += sec < 10 ? "0" : "";
      strTime += sec;
      return strTime;
    },
    configuration() {
      const iceServers = this.$store.state.configStore.webrtc.iceServers;
      return {
        iceServers: iceServers
      }
    },
    isVideo() {
      return this.mode == "video"
    },
    modeText() {
      return this.isVideo ? "视频" : "语音";
    },
    isChating() {
      return this.state == "CHATING";
    },
    isWaiting() {
      return this.state == "WAITING";
    },
    isClose() {
      return this.state == "CLOSE";
    }
  },
  mounted() {
    // 初始化音频文件
    this.initAudio();
  },
  created() {
    // 监听页面刷新事件
    window.addEventListener('beforeunload', () => {
      this.onQuit();
    });
  },
  beforeUnmount() {
    this.onQuit();
  }
}
</script>

<style lang="scss">
.rtc-private-video {
  position: relative;

  .el-loading-text {
    color: white !important;
    font-size: 16px !important;
  }

  .path {
    stroke: white !important;
  }

  .rtc-video-box {
    position: relative;
    background-color: #eeeeee;

    .rtc-video-friend {
      height: 70vh;

      .friend-head-image {
        position: absolute;
      }

      video {
        width: 100%;
        height: 100%;
        object-fit: cover;
        transform: rotateY(180deg);
      }
    }

    .rtc-video-mine {
      position: absolute;
      z-index: 99999;
      width: 25vh;
      right: 0;
      bottom: -1px;

      video {
        width: 100%;
        object-fit: cover;
        transform: rotateY(180deg);
      }
    }
  }

  .rtc-voice-box {
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 300px;
    background-color: var(--im-color-primary-light-9);

    .rtc-voice-name {
      text-align: center;
      font-size: 20px;
      font-weight: 600;
    }
  }

  .rtc-control-bar {
    display: flex;
    justify-content: space-around;
    padding: 10px;

    .icon {
      font-size: 50px;
      cursor: pointer;
    }
  }
}
</style>
